﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Collections;
using System.Globalization;
using System.Diagnostics;
using System.Text.RegularExpressions;


namespace Engine.Dtd.Schema {
    /// <summary>
    /// Provides DTD parsing and support for the SgmlReader framework.
    /// </summary>
    internal sealed class DtdReader {
        private EntityReader entityReader;
        private EntityResolver entityResolver;
        private DNameCollection nameCollection;
        public DtdReaderState ReadState { get; private set; }
        public Dictionary<string, DEntity> ParameterEntities { get; private set; }

        public DtdReader(Entity entity, DNameCollection nameCollection, EntityResolver resolver) {
            Guard.ArgumentNotNull(entity, "entity");
            Guard.ArgumentNotNull(nameCollection, "nameCollection");
            entityReader = new EntityReader(entity);
            ParameterEntities = new Dictionary<string, DEntity>();
            this.nameCollection = nameCollection;
            this.entityResolver = resolver;
            }

        private char ReadChar() {
            entityReader.ReadChar();
            if (char.IsWhiteSpace(entityReader.CurrentChar))
                return entityReader.SkipWhitespace();
            else
                return entityReader.CurrentChar;
            }

        public bool Read() {
            do {
                switch (ReadChar()) {
                    case Characters.EndOfFile:
                        EntityManager.PopEntity(entityReader);
                        if (entityReader.EndOfFile)
                            return false;
                        else
                            ReadState = DtdReaderState.ReadStartEntity;
                        break;
                    case Characters.StartElement:
                        if (entityReader.ReadChar() == Characters.ExclamationPoint)
                            ReadState = DtdReaderState.ReadStartEntity;
                        else
                            throw new DtdException("Invalid start declaration. Expecting declaration starting with '<!' or '<-'.", entityReader.Context());
                        break;
                    case Characters.EnDash:
                        if (entityReader.ReadChar() == Characters.EnDash)
                            ReadState = DtdReaderState.ReadComment;
                        else
                            throw new DtdException("Invalid comment declaration.", entityReader.Context());
                        break;
                    case Characters.StartSquareBracket:
                        switch (ReadMarkedSectionName()) {
                            case "INCLUDE":
                                if (entityReader.SkipWhitespace() == Characters.StartSquareBracket)
                                    ReadState = DtdReaderState.ReadStartEntity;
                                else
                                    throw new DtdException("Invalid character. Expecting '['.", entityReader.Context());
                                break;
                            case "IGNORE":
                                if (entityReader.SkipWhitespace() == Characters.StartSquareBracket)
                                    ReadState = DtdReaderState.ReadIgnoreSection;
                                else
                                    throw new DtdException("Invalid character. Expecting '['.", entityReader.Context());
                                entityReader.ReadToEnd("Conditional Section", "]]>");
                                break;
                            default:
                                throw new DtdException("Unsupported marked section type. Expecting sections: 'INCLUDE', 'IGNORE'.", entityReader.Context());
                            }
                        break;
                    case Characters.EndSquareBracket:
                        entityReader.ReadToEnd("Conditional Section", ">");
                        ReadState = DtdReaderState.ReadStartEntity;
                        break;
                    case Characters.Percent:
                        entityReader.ReadChar();
                        string name = entityReader.ReadNameToken();
                        entityReader.ReadChar();
                        EntityManager.PushEntity(entityReader, ResolveEntity((DExternalEntity)ParameterEntities[name]));
                        ReadState = DtdReaderState.ReadStartEntity;
                        break;
                    default:
                        Debug.Assert(char.IsLetter(entityReader.CurrentChar), "Invalid character. Expecting letter character.");
                        switch (entityReader.ReadNameToken()) {
                            case "ENTITY":
                                switch (entityReader.SkipWhitespace()) {
                                    case Characters.Percent:
                                        entityReader.ReadChar();
                                        ReadState = DtdReaderState.ReadParameterEntity;
                                        break;
                                    default:
                                        ReadState = DtdReaderState.ReadGeneralEntity;
                                        break;
                                    }
                                break;
                            case "ELEMENT":
                                ReadState = DtdReaderState.ReadElement;
                                break;
                            case "ATTLIST":
                                ReadState = DtdReaderState.ReadAttributeDeclaration;
                                break;
                            case "NOTATION":
                                ReadState = DtdReaderState.ReadNotation;
                                break;
                            default:
                                throw new DtdException("Invalid token. Expecting tokens: 'ENTITY', 'ELEMENT' or 'ATTLIST'.", entityReader.Context());
                            }
                        break;
                    }
                } while (ReadState == DtdReaderState.ReadStartEntity);
            return true;
            }

        private Entity ResolveEntity(DExternalEntity entity) {
            try {
                return new Entity(
                    entity.Name,
                    entityResolver.BaseUrl,
                    entityResolver.Resolve(entity.Public, entity.System)
                    );
                }
            catch (IOException) {
                throw new DtdException(string.Format("Could not resolve an external entity '{0}'. {1}.", entity.Name, entityReader.Context()));
                }
            catch (DtdException) {
                throw new DtdException(string.Format("Could not resolve an external entity '{0}'. {1}.", entity.Name, entityReader.Context()));
                }
            }

        private char ReadCommentDeclarations() {
            char ch = entityReader.CurrentChar;
            while (ch == Characters.EnDash)
                ch = ReadCommentDeclaration(true);
            return ch;
            }

        private char ReadCommentDeclaration(bool full) {
            // This method scans over a comment inside a markup declaration.
            char ch = entityReader.ReadChar();
            if (full && ch != Characters.EnDash)
                throw new DtdException(string.Format("Expecting comment delimiter '--' but found {0}", ch), entityReader.Context());
            entityReader.ReadToEnd("Markup Comment", "--");
            return entityReader.SkipWhitespace();
            }

        private string ReadName() {
            // Skip whitespace, scan name (which may be parameter entity reference
            // which is then expanded to a name)
            char ch = entityReader.SkipWhitespace();
            if (ch == Characters.Percent) {
                DEntity e = GetParameterEntity();
                if (e.External)
                    throw new NotSupportedException("External parameter entity resolution not supported.");
                return e.Literal;
                }
            else
                return entityReader.ReadPrefixNameToken();
            }

        private DEntity GetParameterEntity() {
            // Almost the same as this.current.ScanToken, except we also terminate on ';'
            entityReader.ReadChar();
            string name = entityReader.ReadToken();
            if (entityReader.CurrentChar == Characters.SemiColon)
                entityReader.ReadChar();

            DEntity result = null;
            ParameterEntities.TryGetValue(name, out result);
            if (result == null)
                throw new DtdException(string.Format("Reference to undefined parameter entity '{0}' was not found.", name), entityReader.Context());
            return result;
            }

        public DNotation ReadNotation() {
            entityReader.SkipWhitespace();
            DNotation notation = new DNotation {
                Name = entityReader.ReadNameToken()
            };
            char ch = entityReader.SkipWhitespace();
            switch (entityReader.ReadNameToken()) {
                case "SYSTEM":
                    entityReader.SkipWhitespace();
                    ch = entityReader.CurrentChar;
                    if (ch == Characters.QuotationMark || ch == Characters.Apostrophe) {
                        notation.System = entityReader.ReadLiteral(ch);
                        ch = entityReader.SkipWhitespace();
                        }
                    break;
                case "PUBLIC":
                    entityReader.SkipWhitespace();
                    ch = entityReader.CurrentChar;
                    if (ch == Characters.QuotationMark || ch == Characters.Apostrophe) {
                        notation.Public = entityReader.ReadLiteral(ch);
                        ch = entityReader.SkipWhitespace();
                        }
                    if (ch == Characters.QuotationMark || ch == Characters.Apostrophe) {
                        notation.System = entityReader.ReadLiteral(ch);
                        ch = entityReader.SkipWhitespace();
                        }
                    break;
                default:
                    throw new DtdException("Invalid token. Expecting tokens: 'PUBLIC' or 'SYSTEM'.", entityReader.Context());
                }
            if (ch != Characters.EndElement)
                throw new DtdException(string.Format("Expecting end of NOTATION '>' but found '{0}'", ch), entityReader.Context());
            return notation;
            }

        public DEntity ReadEntity() {
            entityReader.SkipWhitespace();
            string name = entityReader.ReadNameToken();
            entityReader.SkipWhitespace();

            if (ReadState == DtdReaderState.ReadParameterEntity) {
                DEntity entity = ReadParameterEntity(name);
                if (ParameterEntities.ContainsKey(entity.Name))
                    return ParameterEntities[entity.Name];
                else
                    ParameterEntities[entity.Name] = entity;
                return entity;
                }
            else
                return ReadGeneralEntity(name);
            }

        private DEntity ReadGeneralEntity(string name) {
            char ch = entityReader.CurrentChar;
            if (ch == Characters.QuotationMark || ch == Characters.Apostrophe)
                return ReadParameterEntity(name);

            DEntity entity = null;
            LiteralType literalType;
            string token = entityReader.ReadNameToken();
            ch = entityReader.SkipWhitespace();
            if (DEntity.TryParseLiteralType(token, out literalType))
                entity = new DEntity {
                    Name = name,
                    Literal = entityReader.ReadLiteral(ch),
                    LiteralType = literalType
                };
            else {
                DExternalEntity exEntity = ReadExternalEntity(name, token);
                EntityManager.PushEntity(entityReader, ResolveEntity(exEntity));
                exEntity.Literal = entityReader.ReadToEnd();
                EntityManager.PopEntity(entityReader);
                return exEntity;
                }
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch != Characters.EndElement)
                throw new DtdException(string.Format("Expecting end of entity declaration '>' but found '{0}'", ch), entityReader.Context());
            return entity;
            }

        private DEntity ReadParameterEntity(string name) {
            char ch = entityReader.CurrentChar;
            DEntity entity = null;
            if (ch == Characters.QuotationMark || ch == Characters.Apostrophe) {
                entity = new DEntity {
                    Name = name,
                    Literal = entityReader.ReadLiteral(ch),
                    LiteralType = LiteralType.SData
                };
                }
            else
                return ReadExternalEntity(name);
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch != Characters.EndElement)
                throw new DtdException(string.Format("Expecting end of entity declaration '>' but found '{0}'", ch), entityReader.Context());
            return entity;
            }

        private DEntity ReadExternalEntity(string name) {
            entityReader.SkipWhitespace();
            return ReadExternalEntity(name, entityReader.ReadNameToken());
            }

        private DExternalEntity ReadExternalEntity(string name, string token) {
            DExternalEntity entity = new DExternalEntity {
                External = true,
                Name = name,
                LiteralType = Schema.LiteralType.SData
            };
            char ch = entityReader.SkipWhitespace();
            if (string.Equals(token, "PUBLIC", StringComparison.Ordinal))
                entity.Public = entityReader.ReadLiteral(ch);
            else
                entity.System = entityReader.ReadLiteral(ch);
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.QuotationMark || ch == Characters.Apostrophe) {
                entity.System = entityReader.ReadLiteral(ch);
                ch = entityReader.SkipWhitespace();
                }
            if (ch != Characters.EndElement)
                throw new DtdException(string.Format("Expecting end of entity declaration '>' but found '{0}'", ch), entityReader.Context());
            return entity;
            }

        public IEnumerable<DElementDeclaration> ReadElementDeclaration() {
            entityReader.SkipWhitespace();
            ICollection<DName> names = ReadNames();
            char ch = Char.ToUpperInvariant(entityReader.SkipWhitespace());
            // Read element properties
            bool startTagOptional = false;
            bool endTagOptional = false;
            if (ch == Characters.TagOptional || ch == Characters.TagNotOptional) {
                startTagOptional = (ch == Characters.TagOptional);
                entityReader.ReadChar();
                ch = Char.ToUpperInvariant(entityReader.SkipWhitespace());
                if (ch == Characters.TagOptional || ch == Characters.TagNotOptional) {
                    endTagOptional = (ch == Characters.TagOptional);
                    ch = entityReader.ReadChar();
                    }
                }
            // Read content model
            ch = entityReader.SkipWhitespace();
            DContentModelType contentType;
            DModelGroups modelGroups = ReadContentModel(out contentType);
            DContentModel contentModel = new DContentModel(modelGroups);
            contentModel.ContentType = contentType;
            ch = entityReader.SkipWhitespace();
            // Read exclusions
            if (ch == Characters.EnDash) {
                ch = entityReader.ReadChar();
                if (ch == Characters.LeftParenthesis) {
                    contentModel.Exclusions = ReadNames();
                    ch = entityReader.SkipWhitespace();
                    }
                else if (ch == Characters.EnDash)
                    ch = ReadCommentDeclaration(false);
                else
                    throw new DtdException(string.Format("Invalid syntax at '{0}'", ch), entityReader.Context());
                }
            // Read inclusions
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch == Characters.Plus) {
                ch = entityReader.ReadChar();
                if (ch != Characters.LeftParenthesis)
                    throw new DtdException(string.Format("Expecting inclusions name group but found '{0}' symbol.", ch), entityReader.Context());
                contentModel.Inclusions = new InclusionCollection(contentModel, ReadNames());
                ch = entityReader.SkipWhitespace();
                }
            // Read comment of declaration
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch != Characters.EndElement)
                throw new DtdException(string.Format("Expecting end of ELEMENT declaration '>' but found '{0}' symbol.", ch), entityReader.Context());

            List<DElementDeclaration> elements = new List<DElementDeclaration>(names.Count);
            foreach (DName item in names) {
                elements.Add(new DElementDeclaration(contentModel) {
                    Name = item,
                    StartTagOptional = startTagOptional,
                    EndTagOptional = endTagOptional
                });
                }
            return elements;
            }

        private string[] ReadAttributeValues(char ch) {
            ICollection<string> names = new List<string>();
            if (ch == Characters.LeftParenthesis) {
                entityReader.ReadChar();
                ch = entityReader.SkipWhitespace();
                while (ch != Characters.RightParenthesis) {
                    string token = entityReader.ReadToken();
                    names.Add(token.ToUpperInvariant());
                    switch (entityReader.SkipWhitespace()) {
                        case Characters.VerticalBar:
                            entityReader.ReadChar();
                            ch = entityReader.SkipWhitespace();
                            break;
                        case Characters.Percent:
                            DEntity entity = GetParameterEntity();
                            EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
                            ch = entityReader.CurrentChar;
                            break;
                        case Characters.EndOfFile:
                            EntityManager.PopEntity(entityReader);
                            ch = entityReader.CurrentChar;
                            break;
                        default:
                            ch = entityReader.CurrentChar;
                            break;
                        }
                    }
                // Consume ')'
                entityReader.ReadChar();
                }
            else
                throw new DtdException("Expecting name group '('.", entityReader.Context());
            return names.ToArray();
            }

        private string[] ReadNotationAttributeType(char ch) {
            ICollection<string> names = new List<string>();
            if (ch == Characters.LeftParenthesis) {
                entityReader.ReadChar();
                ch = entityReader.SkipWhitespace();
                while (ch != Characters.RightParenthesis) {
                    string token = entityReader.ReadNameToken();
                    token = token.ToUpper(CultureInfo.InvariantCulture);
                    names.Add(token);
                    ch = entityReader.SkipWhitespace();
                    if (ch == Characters.VerticalBar)
                        entityReader.ReadChar();
                    ch = entityReader.SkipWhitespace();
                    }
                // Consume ')'
                entityReader.ReadChar();
                }
            else
                throw new DtdException("Expecting name group '('.", entityReader.Context());
            return names.ToArray();
            }

        private ICollection<DName> ReadNames() {
            ICollection<DName> names = new List<DName>();
            char ch = entityReader.CurrentChar;
            if (ch == Characters.LeftParenthesis) {
                entityReader.ReadChar();
                ch = entityReader.SkipWhitespace();
                while (ch != Characters.RightParenthesis) {
                    // Skip whitespace, scan name (which may be parameter entity reference
                    // which is then expanded to a name)                    
                    ch = entityReader.SkipWhitespace();
                    if (ch == Characters.Percent) {
                        DEntity entity = GetParameterEntity();
                        ReadEntityLiteral(entity, ref names);
                        ch = entityReader.CurrentChar;
                        }
                    else
                        names.Add(nameCollection.Add(entityReader.ReadNameToken()));
                    ch = entityReader.SkipWhitespace();
                    if (ch == Characters.VerticalBar || ch == Characters.Comma)
                        ch = entityReader.ReadChar();
                    }
                // Consume ')'
                entityReader.ReadChar();
                }
            else
                names.Add(nameCollection.Add(entityReader.ReadNameToken()));
            return names;
            }

        internal void ReadEntityLiteral(DEntity entity, ref ICollection<DName> names) {
            EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
            char ch = entityReader.CurrentChar;
            ch = entityReader.SkipWhitespace();
            while (ch != Characters.EndOfFile) {
                if (ch == Characters.LeftParenthesis)
                    ch = entityReader.ReadChar();
                if (ch == Characters.Percent)
                    ReadEntityLiteral(GetParameterEntity(), ref names);
                else {
                    if (ch == Characters.Hash)
                        entityReader.ReadChar();
                    names.Add(nameCollection.Add(entityReader.ReadNameToken()));
                    }
                ch = entityReader.SkipWhitespace();
                if (ch == Characters.RightParenthesis)
                    ch = entityReader.ReadChar();
                if (ch == Characters.QueryMark || ch == Characters.Asterix || ch == Characters.Plus) {
                    entityReader.ReadChar();
                    ch = entityReader.SkipWhitespace();
                    if (ch == Characters.RightParenthesis)
                        ch = entityReader.ReadChar();
                    }
                if (ch == Characters.VerticalBar || ch == Characters.Ampersand || ch == Characters.Comma) {
                    entityReader.ReadChar();
                    ch = entityReader.SkipWhitespace();
                    }
                }
            EntityManager.PopEntity(entityReader);
            }

        private DModelGroups ReadContentModel(out DContentModelType contentType) {
            contentType = DContentModelType.Default;
            DModelGroups model = null;
            char ch = entityReader.CurrentChar;
            if (ch == Characters.LeftParenthesis) {
                entityReader.ReadChar();
                model = new DModelGroups();
                ReadContentModel(ref model);
                }
            else if (ch == Characters.Percent) {
                DEntity entity = GetParameterEntity();
                EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
                model = ReadContentModel(out contentType);
                EntityManager.PopEntity(entityReader);
                }
            else {
                contentType = DElementDeclaration.ParseContentModelType(ReadName());
                model = new DModelGroups();
                switch (contentType) {
                    case DContentModelType.Default:
                        throw new InvalidOperationException("The content type 'Default' could not be empty.");
                    case DContentModelType.CData:
                        model.Add(new DElement(DName.CData, DElementOccurrence.ZeroOrMore));
                        break;
                    case DContentModelType.RCData:
                    case DContentModelType.Empty:
                        break;
                    default:
                        throw new InvalidOperationException("Unknown the content type of a model.");
                    }
                }
            return model;
            }

        private string ReadMarkedSectionName() {
            ReadChar();
            return ReadName();
            }

        public string ReadComment() {
            return entityReader.ReadToEnd("Comment", "-->");
            }

        private DModelGroups ReadContentModel(ref DModelGroups model) {
            // Called when part of the model is made up of the contents of a parameter entity
            // The entity must terminate while inside the content model.
            Stack<DModelGroups> stack = null;
            while (entityReader.SkipWhitespace() != Characters.RightParenthesis || (stack == null || stack.Count > 0)) {
                char ch = entityReader.CurrentChar;
                if (ch == Characters.EndOfFile)
                    break;
                if (ch == Characters.Percent) {
                    DEntity entity = GetParameterEntity();
                    EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
                    ReadContentModel(ref model);
                    EntityManager.PopEntity(entityReader);
                    ch = entityReader.SkipWhitespace();
                    if (ch == Characters.EndOfFile)
                        break;
                    }
                if (ch == Characters.LeftParenthesis) {
                    if (stack == null)
                        stack = new Stack<DModelGroups>();
                    stack.Push(model);
                    model = new DModelGroups();
                    entityReader.ReadChar();
                    continue;
                    }
                else if (ch == Characters.RightParenthesis) {
                    ch = entityReader.ReadChar();
                    if (ch == Characters.Asterix || ch == Characters.Plus || ch == Characters.QueryMark) {
                        model.Occurrence = DElement.ParseOccurrence(ch);
                        ch = entityReader.ReadChar();
                        }
                    if (stack == null || stack.Count == 0)
                        break;
                    DModelGroups parent = stack.Pop();
                    parent.Add(model);
                    model = parent;
                    continue;
                    }
                if (ch == Characters.Comma || ch == Characters.VerticalBar || ch == Characters.Ampersand) {
                    model.OrderType = DModelGroups.ParseGroupOrderType(ch);
                    entityReader.ReadChar();
                    }
                else {
                    // Since '#' is not a valid name character.
                    if (ch == Characters.Hash)
                        entityReader.ReadChar();
                    string token = entityReader.ReadNameToken();
                    ch = entityReader.CurrentChar;
                    if (ch == Characters.QueryMark || ch == Characters.Plus || ch == Characters.Asterix) {
                        model.Add(new DElement(nameCollection.Add(token), DElement.ParseOccurrence(ch)));
                        entityReader.ReadChar();
                        }
                    else
                        model.Add(new DElement(nameCollection.Add(token)));
                    }
                }
            if (entityReader.CurrentChar == Characters.RightParenthesis) {
                char ch = entityReader.ReadChar();
                if (ch == Characters.QueryMark || ch == Characters.Plus || ch == Characters.Asterix) {
                    model.Occurrence = DElement.ParseOccurrence(ch);
                    entityReader.ReadChar();
                    }
                }
            return model;
            }

        public DAttributeDeclaration ReadAttributeDeclaration() {
            ReadChar();
            return new DAttributeDeclaration(
                ReadNames(),
                ReadAttributes(Characters.EndElement)
                );
            }

        private IEnumerable<DAttribute> ReadAttributes(char terminator) {
            ICollection<DAttribute> attributes = new List<DAttribute>();
            char ch = entityReader.SkipWhitespace();
            while (ch != terminator) {
                switch (ch) {
                    case Characters.Percent:
                        DEntity entity = GetParameterEntity();
                        EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
                        ch = entityReader.SkipWhitespace();
                        break;
                    case Characters.EnDash:
                        ch = ReadCommentDeclarations();
                        break;
                    case Characters.EndOfFile:
                        EntityManager.PopEntity(entityReader);
                        ch = entityReader.SkipWhitespace();
                        break;
                    default:
                        attributes.Add(ReadAttributeDeclaration(ref ch));
                        break;
                    }
                }
            return attributes.ToArray();
            }

        private DAttribute ReadAttributeDeclaration(ref char ch) {
            DAttribute attribute = new DAttribute(nameCollection.Add(ReadName()));
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch == Characters.EndOfFile) {
                EntityManager.PopEntity(entityReader);
                ch = entityReader.SkipWhitespace();
                }
            ReadAttributeType(ch, attribute);
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch == Characters.EndOfFile) {
                EntityManager.PopEntity(entityReader);
                ch = entityReader.SkipWhitespace();
                }
            ReadAttributePresence(ch, attribute);
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch == Characters.EndOfFile) {
                EntityManager.PopEntity(entityReader);
                ch = entityReader.SkipWhitespace();
                }
            ReadDefaultValue(ch, attribute);
            ch = entityReader.SkipWhitespace();
            if (ch == Characters.EnDash)
                ch = ReadCommentDeclarations();
            if (ch == Characters.EndOfFile) {
                EntityManager.PopEntity(entityReader);
                ch = entityReader.SkipWhitespace();
                }
            return attribute;
            }

        private void ReadAttributeType(char ch, DAttribute attribute) {
            // Parameter entity reference
            if (ch == Characters.Percent) {
                DEntity entity = GetParameterEntity();
                EntityManager.PushEntity(entityReader, entity.Name, entity.Literal);
                ReadAttributeType(entityReader.CurrentChar, attribute);
                EntityManager.PopEntity(entityReader);
                return;
                }
            // Enumerated type
            if (ch == Characters.LeftParenthesis) {
                attribute.Initialize(ReadAttributeValues(ch));
                attribute.AttributeType = DAttributeType.Enumeration;
                }
            else {
                string token = ReadName();
                // Enumerated Notation type
                if (string.Equals(token, "NOTATION", StringComparison.InvariantCultureIgnoreCase)) {
                    ch = entityReader.SkipWhitespace();
                    attribute.Initialize(ReadNotationAttributeType(ch));
                    attribute.AttributeType = DAttributeType.Notation;
                    }
                // Tokenized type
                // String type
                else
                    attribute.AttributeType = DAttribute.ParseType(token);
                }
            }

        private void ReadAttributePresence(char ch, DAttribute attribute) {
            if (ch == Characters.Percent) {
                DEntity e = GetParameterEntity();
                EntityManager.PushEntity(entityReader, e.Name, e.Literal);
                ReadAttributePresence(entityReader.CurrentChar, attribute);
                EntityManager.PopEntity(entityReader);
                }
            else if (ch == Characters.Hash) {
                entityReader.ReadChar();
                attribute.Presence = DAttribute.ParsePresence(entityReader.ReadNameToken());
                }
            }

        private void ReadDefaultValue(char ch, DAttribute attribute) {
            if (attribute.Presence == DAttributePresence.Default || attribute.Presence == DAttributePresence.Fixed) {
                if (ch == Characters.Apostrophe || ch == Characters.QuotationMark) {
                    if (entityReader.PeekChar() == Characters.Percent) {
                        entityReader.ReadChar();
                        string token = entityReader.ReadLiteral(Characters.SemiColon);
                        attribute.Default = ParameterEntities[token].Literal;
                        entityReader.ReadChar();
                        }
                    else
                        attribute.Default = entityReader.ReadLiteral(ch);
                    }
                else {
                    // Must be one of the enumerated names.
                    attribute.Default = entityReader.ReadToken();
                    }
                }
            }
        }
    }